/* jtk-gtk.c */

#include <jtk.h>
#include <gtk/gtk.h>

/* We might occasionally refer to these two widgets, so make them
 * global. */
static GtkWidget *mainwin;
static GtkWidget *canvas;

/* Function prototypes for event handling. */
static void     jtk_gtk_window_destroy(GtkWidget *widget, GdkEventKey *event, gpointer data);
static void     jtk_gtk_draw(GtkWidget *widget, GdkEventExpose *event, gpointer data);
static void     jtk_gtk_key_down(GtkWidget *widget, GdkEventKey *event, gpointer data);
static gboolean jtk_gtk_mouse_down(GtkWidget *widget, GdkEventButton *event, gpointer data);
static gboolean jtk_gtk_mouse_up(GtkWidget *widget, GdkEventButton *event, gpointer data);
static gboolean jtk_gtk_mouse_drag(GtkWidget *widget, GdkEventMotion *event, gpointer data);

void jtk_init(const char *title, int width, int height)
{
    int i;

    for (i = 0; i < JTK_TOTAL_CALLBACKS; i++)
        jtk_callback_list[i] = JTK_NULL_CALLBACK;

    gtk_init(NULL, NULL);

    mainwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(mainwin), title);
    gtk_window_set_position(GTK_WINDOW(mainwin), GTK_WIN_POS_CENTER);

    /* We only expects a single drawing area extents to the 
     * whole window, so just create a canvas will be fine. */
    canvas = gtk_drawing_area_new();
    gtk_widget_set_events(canvas,
                          GDK_EXPOSURE_MASK       |
                          GDK_KEY_PRESS_MASK      |
                          GDK_BUTTON1_MOTION_MASK |
                          GDK_BUTTON_PRESS_MASK   |
                          GDK_BUTTON_RELEASE_MASK);

    /* Because GTK+ has automatic sizing, we can restrict 
     * window size by specify it's content size (aka. the
     * canvas' size) */
    gtk_widget_set_size_request(canvas, width, height);

    /* If main window is destroied (closed), we should quit this 
     * program imediately (some systems does not do that by 
     * default) */
    g_signal_connect(G_OBJECT(mainwin), "destroy",
                     G_CALLBACK(jtk_gtk_window_destroy), NULL);

    /* expose-event handles all the real drawing stuff */
    g_signal_connect(G_OBJECT(canvas), "expose-event",
                     G_CALLBACK(jtk_gtk_draw), NULL);

    /* The following 4 events are used to receive user inputs */
    g_signal_connect(G_OBJECT(canvas), "key-press-event",
                     G_CALLBACK(jtk_gtk_key_down), NULL);
    g_signal_connect(G_OBJECT(canvas), "button-press-event",
                     G_CALLBACK(jtk_gtk_mouse_down), NULL);
    g_signal_connect(G_OBJECT(canvas), "button-release-event",
                     G_CALLBACK(jtk_gtk_mouse_up), NULL);
    g_signal_connect(G_OBJECT(canvas), "motion-notify-event",
                     G_CALLBACK(jtk_gtk_mouse_drag), NULL);

    g_object_set(G_OBJECT(canvas), "can_focus", 1, NULL);

    gtk_container_add(GTK_CONTAINER(mainwin), canvas);

    gtk_widget_show_all(mainwin);
}

void jtk_main_loop()
{
    /* enter main loop */
    gtk_main();
}

void jtk_redraw()
{
    gtk_widget_queue_draw(canvas);
}

void jtk_quit()
{
    gtk_main_quit();
}

static enum jtk_event_type jtk_event_type_from_gtk(GdkEventType type)
{
    switch (type)
    {
    case GDK_BUTTON_PRESS:
        return JTK_EVENT_MOUSE_DOWN;

    case GDK_BUTTON_RELEASE:
        return JTK_EVENT_MOUSE_UP;

    case GDK_MOTION_NOTIFY:
        return JTK_EVENT_MOUSE_DRAG;

    case GDK_KEY_PRESS:
        return JTK_EVENT_KEY_DOWN;

    default: 
        return 0;
    }
}

static jtk_event jtk_mouse_event_from_gtk(GdkEventButton *event)
{
    jtk_event evt;

    evt.type = jtk_event_type_from_gtk(event->type);
    evt.u.point.x = event->x;
    evt.u.point.y = event->y;

    /* evt.timestamp expects a double value in seconds, but 
     * event->time provides a guint32 in milliseconds, so we
     * must do the normalization here. */
    evt.timestamp = (double) event->time / 1000.0;

    return evt;
}

static jtk_event jtk_key_event_from_gtk(GdkEventKey *event)
{
    jtk_event evt;

    evt.type = jtk_event_type_from_gtk(event->type);

    /* event->keyval might not be quite safe, but it's the 
     * best guess we can have, and it's quite sufficient 
     * here. */
    evt.u.key = event->keyval;
    evt.timestamp = (double) event->time / 1000.0;

    return evt;
}

static void jtk_gtk_window_destroy(GtkWidget *widget,
                                   GdkEventKey *event,
                                   gpointer data)
{
    gtk_main_quit();
}

static void jtk_gtk_draw(GtkWidget *widget,
                         GdkEventExpose *event,
                         gpointer data)
{
    cairo_t *cr;

    if (jtk_callback_list[JTK_DRAW] == JTK_NULL_CALLBACK)
        return;

    /* Our drawing callback expects a cairo_t to render on. */
    cr = gdk_cairo_create(widget->window);
    jtk_callback_list[JTK_DRAW](cr);
    cairo_destroy(cr);
}

static void jtk_gtk_key_down(GtkWidget *widget,
                             GdkEventKey *event,
                             gpointer data)
{
    if (jtk_callback_list[JTK_KEY_DOWN] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = jtk_key_event_from_gtk(event);
    jtk_callback_list[JTK_KEY_DOWN](&evt);
}

static gboolean jtk_gtk_mouse_down(GtkWidget *widget,
                                   GdkEventButton *event,
                                   gpointer data)
{
    if (jtk_callback_list[JTK_MOUSE_DOWN] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = jtk_mouse_event_from_gtk(event);
    jtk_callback_list[JTK_MOUSE_DOWN](&evt);

    return TRUE;
}

static gboolean jtk_gtk_mouse_up(GtkWidget *widget,
                                 GdkEventButton *event,
                                 gpointer data)
{
    if (jtk_callback_list[JTK_MOUSE_UP] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = jtk_mouse_event_from_gtk(event);
    jtk_callback_list[JTK_MOUSE_UP](&evt);

    return TRUE;
}

static gboolean jtk_gtk_mouse_drag(GtkWidget *widget,
                                   GdkEventMotion *event,
                                   gpointer data)
{
    if (jtk_callback_list[JTK_MOUSE_DRAG] == JTK_NULL_CALLBACK)
        return;

    jtk_event evt = jtk_mouse_event_from_gtk((GdkEventButton *) event);
    jtk_callback_list[JTK_MOUSE_DRAG](&evt);

    return TRUE;
}

